//
// Created by mabinjie on 2024/10/29.
//

#ifndef GATEWAY_HPP
#define GATEWAY_HPP
#include <memory>
#include <string>
#include <utility>

#include "packet_handler.hpp"
#include "common/storage.hpp"
#include "common/entity/car.hpp"
#include "common/entity/terminal.hpp"

namespace network {

class Gateway {
public:
    Gateway(protocol::Packet packet, handler_t handler) : packet_(std::move(packet)),handler_(std::move(handler)) {

    }

    protocol::Packet route() {
        if (packet_) {
            switch (const auto header = packet_->header_; header.message_id_) {
                case 0x0100: { // registry
                    common::Storage storage;
                    auto bd = dynamic_cast<protocol::terminal::Registeration*>(packet_->body_.get());
                    std::shared_ptr<protocol::platform::RegisterResponse> response;
                    mysqlx::bytes manu_id(bd->manufacturer_id_.bytes, size(bd->manufacturer_id_));
                    mysqlx::bytes ter_id(bd->terminal_id_.bytes, size(bd->terminal_id_));
                    auto terminals = storage.select<common::Terminal>(
                        "terminal", "manufacturer_id=:p1 AND terminal_id=:p2", {{"p1", manu_id}, {"p2", ter_id}});
                    auto cars = storage.select<common::Car>("car", "license=:p1", {{"p1", bd->license_}});
                    if (terminals.empty()) { // 终端不存在
                        logger->info("No terminals found");
                        response = std::make_shared<
                            protocol::platform::RegisterResponse>(
                            header.message_sn_, protocol::platform::RegisterResult::TERMINAL_NOT_IN_DATABASE, "");
                    } else if (1 == terminals[0].status) { // 终端已被注册
                        logger->info("terminal has been registered");
                        response = std::make_shared<protocol::platform::RegisterResponse>(
                            header.message_sn_, protocol::platform::RegisterResult::TERMINAL_ALREADY_REGISTERED, "");
                    } else if (cars.empty()) { // 车辆不存在
                        logger->info("No cars found");
                        response = std::make_shared<protocol::platform::RegisterResponse>(
                            header.message_sn_, protocol::platform::RegisterResult::CAR_NOT_IN_DATABASE, "");
                    } else if (1 == cars[0].status) { // 车辆已被注册
                        logger->info("car has been registered");
                        response = std::make_shared<protocol::platform::RegisterResponse>(
                            header.message_sn_, protocol::platform::RegisterResult::CAR_ALREADY_REGISTERED, "");
                    } else { // 注册成功
                        logger->info("terminal {} successfully registered on car {}", terminals[0].id, cars[0].id);
                        handler_->terminal_id = terminals[0].id;
                        std::string verify_code(bd->license_);
                        storage.transaction([&]() {
                            storage.insert("terminal_registration",
                                       {bd->province_id_, bd->city_id_, terminals[0].id, cars[0].id, verify_code}, {
                                           "province", "city", "terminal", "car", "verify_code"
                                       });
                            storage.update("car", {{"status", 1}}, "id=:p", {{"p", cars[0].id}});
                            storage.update("terminal", {{"status", 1}}, "id=:p", {{"p", terminals[0].id}});
                        });

                        response = std::make_shared<protocol::platform::RegisterResponse>(
                            header.message_sn_, protocol::platform::RegisterResult::SUCCESS, verify_code.data());
                    }
                    return handler_->packet(response, {
                                                                    false, false,
                                                                    static_cast<uint16_t>(size_of(response.get()))
                                                                });
                }
                case 0x0003: { // logout
                    common::Storage storage;
                    auto &hdr = packet_->header_;
                    auto response = std::make_shared<protocol::platform::CommonResponse>(
                        hdr.message_sn_, hdr.message_id_,
                        handler_->terminal_imei.empty()
                            ? protocol::platform::ResponseResult::FAIL
                            : protocol::platform::ResponseResult::SUCCESS);
                    if (!handler_->terminal_imei.empty()) {
                        logger->info("A terminal has logout");
                        storage.transaction([&]() {
                            auto &temp = handler_->terminal_imei;
                            auto remove_terminal = storage.select<common::Terminal>(
                                "terminal", "imei=:p", {{"p", mysqlx::bytes(temp.data(), temp.size())}});
                            auto imei_str = common::toHex(temp.data(), temp.size());
                            if (remove_terminal.empty()) ERR(logger, "No terminals found whose imei = {}", imei_str);
                            int terminal_id = remove_terminal[0].id;
                            auto to_remove = storage.select<common::TerminalRegistration>("terminal_registration", "id=:p", {{"p", terminal_id}});
                            if (to_remove.empty()) ERR(logger, "There is no registry information found for terminal whose imei = {}", imei_str);
                            storage.update("car", {{"status", 0}}, "id=:p", {{"p", to_remove[0].car}});
                            storage.update("terminal", {{"status", 0}}, "id=:p", {{"p", to_remove[0].terminal}});
                            storage.remove("terminal_registration", "id=:p", {{"p", to_remove[0].id}});
                        });
                    }
                    return handler_->packet(
                        response, {false, false, (uint16_t) size_of(response.get())});
                }
                case 0x0102: { // verification
                    std::shared_ptr<protocol::platform::CommonResponse> response;
                    auto bd = dynamic_cast<protocol::terminal::Verification*>(packet_->body_.get());
                    logger->debug("A terminal with imei {} want to login with verify code {}",
                                  common::toHex(bd->imei_.bytes, size(bd->imei_)), std::string(bd->auth_code_->data));
                    mysqlx::bytes imei(bd->imei_.bytes, size(bd->imei_));
                    common::Storage storage;
                    auto terminal = storage.select<common::Terminal>("terminal", "imei=:p", {{"p", imei}});
                    auto registration = terminal.empty() ? std::vector<common::TerminalRegistration>() : storage.select<common::TerminalRegistration>(
                        "terminal_registration", "terminal=:p", {{"p", terminal[0].id}});
                    if (registration.empty()) {
                        logger->info("No registration found");
                        response = std::make_shared<protocol::platform::CommonResponse>(
                            packet_->header_.message_sn_, packet_->header_.message_id_,
                            protocol::platform::ResponseResult::MESSAGE_ERROR);
                    } else if (registration[0].verify_code != std::string(bd->auth_code_->data)) {
                        logger->info("Registration verification failed");
                        response = std::make_shared<protocol::platform::CommonResponse>(
                            packet_->header_.message_sn_, packet_->header_.message_id_,
                            protocol::platform::ResponseResult::FAIL);
                    } else {
                        handler_->terminal_imei = terminal[0].imei;
                        logger->info("Registration verification successful");
                        response = std::make_shared<protocol::platform::CommonResponse>(
                            packet_->header_.message_sn_, packet_->header_.message_id_,
                            protocol::platform::ResponseResult::SUCCESS);
                    }

                    return handler_->packet(
                        response, {false, false, uint16_t(size_of(response.get()))});
                }
                case 0x0002: { // heartbeat from terminal
                    auto &hdr = packet_->header_;
                    auto response = std::make_shared<protocol::platform::CommonResponse>(
                        hdr.message_sn_, hdr.message_id_, protocol::platform::ResponseResult::SUCCESS);
                    return handler_->packet(
                        response, {false, false, (uint16_t) size_of(response.get())});
                }
                case 0x0004: {
                    auto response = std::make_shared<protocol::platform::ServerTimeRes>(common::BcdTime());
                    return handler_->packet(
                        response, {false, false, (uint16_t) size_of(response.get())});
                }
                default: return nullptr;
            }
        }
        return {};
    }

    uint16_t message_type() const {
        return packet_->header_.message_id_;
    }

    std::string host() {
        if (has_source_)
            return host_;
        return {};
    }

    std::string port() {
        if (has_source_)
            return port_;
        return {};
    }

    protocol::Packet packet() {
        return packet_;
    }

private:
    protocol::Packet packet_;
    handler_t handler_;
    std::string host_, port_;
    bool has_source_ = false;
    std::string from_;

    logger_t logger = common::Logger::logger("Gateway");
};

}

#endif //GATEWAY_HPP
